package com.vehicle.coupon.service.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import ch.qos.logback.core.joran.spi.XMLUtil;
import com.alibaba.fastjson.JSON;
import com.vehicle.common.core.domain.R;
import com.vehicle.common.core.web.domain.AjaxResult;
import com.vehicle.coupon.constant.RabbitmqConstants;
import com.vehicle.coupon.domain.CouponRecord;
import com.vehicle.coupon.service.ICouponRecordService;
import io.swagger.annotations.ApiOperation;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import com.vehicle.coupon.mapper.CouponMapper;
import com.vehicle.coupon.domain.Coupon;
import com.vehicle.coupon.service.ICouponService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 优惠券Service业务层处理
 *
 * @author XU
 * @date 2025-03-25
 */
@Service
public class CouponServiceImpl implements ICouponService {

    // 注入优惠券Mapper，用于与数据库中优惠券表进行交互
    @Resource
    private CouponMapper couponMapper;

    // 注入优惠券记录服务，用于处理优惠券领取记录相关业务
    @Autowired
    private ICouponRecordService couponRecordService;

    // 注入Redis模板，用于操作Redis缓存
    @Autowired
    private StringRedisTemplate redisTemplate;

    // 注入RabbitMQ模板，用于向RabbitMQ发送消息
    @Autowired
    RabbitTemplate rabbitTemplate;

    // 定义Redis中优惠券库存的键前缀
    private static final String COUPON_STOCK_KEY = "coupon:stock:";
    // 定义Redis中优惠券分布式锁的键前缀
    private static final String COUPON_LOCK_KEY = "coupon:lock:";

    /**
     * 发放优惠券的业务逻辑实现
     *
     * @param couponId     优惠券ID
     * @param userId       发放人ID
     * @param targetUserId 领取人ID
     * @return 发放成功返回true，失败返回false
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean issueCoupon(Long couponId, Long userId, Long targetUserId) {
        // 1. 检查优惠券状态
        // 根据优惠券ID从数据库中查询优惠券信息
        Coupon coupon1 = couponMapper.selectCouponById(couponId);
        // 如果查询结果为空，说明优惠券不存在，抛出异常
        if (coupon1 == null) {
            throw new RuntimeException("优惠券不存在");
        }

        // 获取当前时间
        Date now = new Date();
        // 检查优惠券是否在有效期内，若当前时间早于优惠券开始时间，抛出异常
        if (now.before(coupon1.getStartTime())) {
            throw new RuntimeException("优惠券活动未开始");
        }
        // 若当前时间晚于优惠券结束时间，抛出异常
        if (now.after(coupon1.getEndTime())) {
            throw new RuntimeException("优惠券活动已结束");
        }

        // 2. 检查用户是否已领取该优惠券
        // 调用Mapper方法检查目标用户是否已经领取过该优惠券
        if (couponMapper.checkUserReceived(couponId, targetUserId) > 0) {
            throw new RuntimeException("该用户已领取过此优惠券");
        }

        // 2. 使用Redis分布式锁防止重复提交
        // 生成分布式锁的键，格式为 coupon:lock:优惠券ID:领取人ID
        String lockKey = COUPON_LOCK_KEY + couponId + ":" + targetUserId;
        // 尝试获取分布式锁，设置锁的过期时间为10秒
        boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        // 如果获取锁失败，说明用户可能重复提交请求，抛出异常
        if (!locked) {
            throw new RuntimeException("请勿重复提交");
        }

        String stockKey = null;
        try {
            // 3. Redis预减库存
            // 生成Redis中优惠券库存的键，格式为 coupon:stock:优惠券ID
            stockKey = COUPON_STOCK_KEY + couponId;
            // 对Redis中该优惠券的库存进行减1操作
            Long stock = redisTemplate.opsForValue().decrement(stockKey);
            // 如果减1后库存小于0，说明库存不足，将库存加回去并返回false
            if (stock < 0) {
                redisTemplate.opsForValue().increment(stockKey);
                return false;
            }

            // 4. 数据库乐观锁更新
            // 再次从数据库中查询优惠券信息
            Coupon coupon = couponMapper.selectCouponById(couponId);
            // 如果查询结果为空或者优惠券剩余数量小于等于0，说明库存不足，抛出异常
            if (coupon == null || coupon.getRemaining() <= 0) {
                throw new RuntimeException("优惠券库存不足");
            }

            // 调用Mapper方法使用乐观锁更新优惠券库存，传入优惠券ID和版本号
            int result = couponMapper.decreaseStock(couponId, coupon.getVersion());
            // 如果更新失败（更新结果小于等于0），说明可能存在并发更新，抛出异常
            if (result <= 0) {
                throw new RuntimeException("领取失败，请重试");
            }

            // 5. 插入领取记录
            // 创建一个新的优惠券领取记录对象
            CouponRecord record = new CouponRecord();
            // 设置优惠券ID
            record.setCouponId(couponId);
            // 设置领取用户ID
            record.setUserId(targetUserId);
            // 设置领取时间为当前时间
            record.setGetTime(new Date());
            // 设置优惠券状态为0
            record.setStatus(0);
            // 设置发放人ID
            record.setUserId(userId);

            // 发送消息到RabbitMQ
            // 将优惠券领取记录对象转换为JSON字符串
            String msg = JSON.toJSONString(record);

            // 通过用户id和优惠券id生成唯一请求id
            String requestId = "coupon_record_copy1:" + targetUserId + "_" + couponId;

            // 将请求ID和对应的消息存入Redis
            redisTemplate.opsForValue().set(requestId, msg);

            // 发送消息到RabbitMQ，指定交换机名称和路由键
            rabbitTemplate.convertAndSend(RabbitmqConstants.EXCHANGE_NAME, RabbitmqConstants.ROUTING_KEY, requestId);
            // 原本的插入领取记录到数据库的操作，现在改为通过消息队列异步处理
            // couponRecordService.insertCouponRecord(record);

            return true;
        } catch (Exception e) {
            // 发生异常，回滚Redis库存
            redisTemplate.opsForValue().increment(stockKey);
            // 抛出异常，事务会回滚
            throw e;
        } finally {
            // 释放分布式锁
            redisTemplate.delete(lockKey);
        }
    }

    /**
     * 根据优惠券ID查询优惠券信息
     *
     * @param id 优惠券ID
     * @return 优惠券对象
     */
    @Override
    public Coupon selectCouponById(Long id) {
        return couponMapper.selectCouponById(id);
    }

    /**
     * 根据条件查询优惠券列表
     *
     * @param coupon 查询条件
     * @return 优惠券列表
     */
    @Override
    public List<Coupon> selectCouponList(Coupon coupon) {
        return couponMapper.selectCouponList(coupon);
    }

    /**
     * 插入新的优惠券信息
     *
     * @param coupon 优惠券对象
     * @return 插入成功的记录数
     */
    @Override
    public int insertCoupon(Coupon coupon) {
        return couponMapper.insertCoupon(coupon);
    }

    /**
     * 更新优惠券信息
     *
     * @param coupon 优惠券对象
     * @return 更新成功的记录数
     */
    @Override
    public int updateCoupon(Coupon coupon) {
        return couponMapper.updateCoupon(coupon);
    }

    /**
     * 根据多个优惠券ID删除优惠券信息
     *
     * @param ids 优惠券ID数组
     * @return 删除成功的记录数
     */
    @Override
    public int deleteCouponByIds(Long[] ids) {
        return couponMapper.deleteCouponByIds(ids);
    }

    /**
     * 根据单个优惠券ID删除优惠券信息
     *
     * @param id 优惠券ID
     * @return 删除成功的记录数
     */
    @Override
    public int deleteCouponById(Long id) {
        return couponMapper.deleteCouponById(id);
    }

    /**
     * 初始化优惠券库存到Redis中
     * 在Bean初始化完成后自动调用该方法
     */
    @PostConstruct
    public void initStock() {
        // 查询所有优惠券信息
        List<Coupon> coupons = couponMapper.selectCouponList(new Coupon());
        // 遍历所有优惠券
        for (Coupon coupon : coupons) {
            // 生成Redis中优惠券库存的键
            String stockKey = COUPON_STOCK_KEY + coupon.getId();
            // 将优惠券的剩余数量存入Redis
            redisTemplate.opsForValue().set(stockKey, String.valueOf(coupon.getRemaining()));
        }
    }
}